libobs_wrapper\display\window_manager/
mod.rs1use std::sync::{
4 atomic::{AtomicBool, Ordering},
5 Arc, Mutex,
6};
7
8use lazy_static::lazy_static;
9use libobs::obs_display_t;
10use windows::{
11 core::{w, HSTRING, PCWSTR},
12 Win32::{
13 Foundation::{COLORREF, HWND, LPARAM, LRESULT, WPARAM},
14 Graphics::Dwm::DwmIsCompositionEnabled,
15 System::{
16 LibraryLoader::{GetModuleHandleA, GetModuleHandleW},
17 SystemInformation::{GetVersionExW, OSVERSIONINFOW},
18 },
19 UI::WindowsAndMessaging::{
20 CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW, GetWindowLongPtrW,
21 LoadCursorW, PostMessageW, PostQuitMessage, RegisterClassExW,
22 SetLayeredWindowAttributes, SetParent, SetWindowLongPtrW, TranslateMessage, CS_HREDRAW,
23 CS_NOCLOSE, CS_OWNDC, CS_VREDRAW, GWL_EXSTYLE, GWL_STYLE, HTTRANSPARENT, IDC_ARROW,
24 LWA_ALPHA, MSG, WM_NCHITTEST, WNDCLASSEXW, WS_CHILD, WS_EX_COMPOSITED, WS_EX_LAYERED,
25 WS_EX_TRANSPARENT, WS_POPUP, WS_VISIBLE,
26 },
27 },
28};
29
30use crate::unsafe_send::Sendable;
31
32mod position_trait;
33mod show_hide;
34mod misc;
35pub use misc::*;
36pub use position_trait::WindowPositionTrait;
37pub use show_hide::ShowHideTrait;
38
39const WM_DESTROY_WINDOW: u32 = 0x8001; extern "system" fn wndproc(
41 window: HWND,
42 message: u32,
43 w_param: WPARAM,
44 l_param: LPARAM,
45) -> LRESULT {
46 unsafe {
47 match message {
48 WM_NCHITTEST => {
49 return LRESULT(HTTRANSPARENT as _);
50 }
51 WM_DESTROY_WINDOW => {
52 PostQuitMessage(0);
53 return LRESULT(0);
54 }
55 _ => {
56 return DefWindowProcW(window, message, w_param, l_param);
57 }
58 }
59 }
60}
61
62fn is_windows8_or_greater() -> windows::core::Result<bool> {
64 let mut os_info: OSVERSIONINFOW = unsafe { std::mem::zeroed() };
65 os_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOW>() as u32;
66
67 unsafe {
68 GetVersionExW(&mut os_info)?;
69 }
70
71 let r = (os_info.dwMajorVersion > 6)
72 || (os_info.dwMajorVersion == 6 && os_info.dwMinorVersion >= 2);
73 Ok(r)
74}
75
76lazy_static! {
77 static ref REGISTERED_CLASS: AtomicBool = AtomicBool::new(false);
78}
79
80fn try_register_class() -> windows::core::Result<()> {
81 if REGISTERED_CLASS.load(Ordering::Relaxed) {
82 return Ok(());
83 }
84
85 unsafe {
86 let instance = GetModuleHandleA(None)?;
87 let cursor = LoadCursorW(None, IDC_ARROW)?;
88
89 let mut style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE;
90
91 let enabled = DwmIsCompositionEnabled()?.as_bool();
92 if is_windows8_or_greater()? || !enabled {
93 style |= CS_OWNDC;
94 }
95
96 let window_class = w!("Win32DisplayClass");
97 let wc = WNDCLASSEXW {
98 cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
99 hCursor: cursor,
100 hInstance: instance.into(),
101 lpszClassName: window_class,
102 style: CS_HREDRAW | CS_VREDRAW,
103 lpfnWndProc: Some(wndproc),
104 cbClsExtra: 0,
105 cbWndExtra: 0,
106 ..Default::default()
107 };
108
109 let atom = RegisterClassExW(&wc as *const _);
110 if atom == 0 {
111 return Err(std::io::Error::last_os_error().into());
112 }
113 }
114
115 REGISTERED_CLASS.store(true, Ordering::Relaxed);
116 Ok(())
117}
118
119#[derive(Debug)]
120pub struct DisplayWindowManager {
121 message_thread: Option<std::thread::JoinHandle<()>>,
123 should_exit: Arc<AtomicBool>,
124 hwnd: Sendable<HWND>,
125
126 x: i32,
127 y: i32,
128
129 width: u32,
130 height: u32,
131
132 scale: f32,
133
134 is_hidden: AtomicBool,
135
136 render_at_bottom: bool,
137
138 pub(super) obs_display: Option<Sendable<*mut obs_display_t>>,
139}
140
141impl DisplayWindowManager {
142 pub fn new(parent: Sendable<HWND>, x: i32, y: i32, width: u32, height: u32) -> anyhow::Result<Self> {
143 let (tx, rx) = oneshot::channel();
144
145 let should_exit = Arc::new(AtomicBool::new(false));
146 let tmp = should_exit.clone();
147
148 let parent = Mutex::new(Sendable(parent));
149 let message_thread = std::thread::spawn(move || {
150 let parent = parent.lock().unwrap().0.clone();
151 let create = move || {
153 log::trace!("Registering class...");
154 try_register_class()?;
155 let win8 = is_windows8_or_greater()?;
156 let enabled = unsafe { DwmIsCompositionEnabled()?.as_bool() };
157
158 let mut window_style = WS_EX_TRANSPARENT;
159 if win8 && enabled {
160 window_style |= WS_EX_COMPOSITED;
161 }
162
163 let instance = unsafe { GetModuleHandleW(PCWSTR::null())? };
164
165 let class_name = HSTRING::from("Win32DisplayClass");
166 let window_name = HSTRING::from("LibObsChildWindowPreview");
167 log::trace!("Creating window...");
168
169 log::debug!(
170 "Creating window with x: {}, y: {}, width: {}, height: {}",
171 x,
172 y,
173 width,
174 height
175 );
176 let window = unsafe {
177 CreateWindowExW(
179 WS_EX_LAYERED,
180 &class_name,
181 &window_name,
182 WS_POPUP | WS_VISIBLE,
183 x,
184 y,
185 width as i32,
186 height as i32,
187 None,
188 None,
189 Some(instance.into()),
190 None,
191 )?
192 };
193
194 log::trace!("HWND is {:?}", window);
195 if win8 || !enabled {
196 log::trace!("Setting attributes alpha...");
197 unsafe {
198 SetLayeredWindowAttributes(window, COLORREF(0), 255, LWA_ALPHA)?;
199 }
200 }
201
202 unsafe {
203 log::trace!("Setting parent...");
204 SetParent(window, Some(parent.0))?;
205 log::trace!("Setting styles...");
206 let mut style = GetWindowLongPtrW(window, GWL_STYLE);
207 style &= !(WS_POPUP.0 as isize);
209 style |= WS_CHILD.0 as isize;
210
211 SetWindowLongPtrW(window, GWL_STYLE, style);
212
213 let mut ex_style = GetWindowLongPtrW(window, GWL_EXSTYLE);
214 ex_style |= window_style.0 as isize;
215
216 SetWindowLongPtrW(window, GWL_EXSTYLE, ex_style);
217 }
218
219 Result::<Sendable<HWND>, anyhow::Error>::Ok(Sendable(window))
220 };
221
222 let r = create();
223 let window = r.as_ref().ok().map(|r| r.0.clone());
224 tx.send(r).unwrap();
225 if window.is_none() {
226 return;
227 }
228 let window = window.unwrap();
229
230 log::trace!("Starting up message thread...");
231 let mut msg = MSG::default();
232 unsafe {
233 while !tmp.load(Ordering::Relaxed)
234 && GetMessageW(&mut msg, Some(window), 0, 0).as_bool()
235 {
236 let _ = TranslateMessage(&msg);
238 DispatchMessageW(&msg);
239 }
240 }
241
242 log::trace!("Exiting message thread...");
243 });
244
245 let window = rx.recv();
246 let window = window??;
247 Ok(Self {
248 x,
249 y,
250 width,
251 height,
252 scale: 1.0,
253 hwnd: window,
254 should_exit,
255 message_thread: Some(message_thread),
256 render_at_bottom: false,
257 is_hidden: AtomicBool::new(false),
258 obs_display: None,
259 })
260 }
261
262 pub fn get_child_handle(&self) -> HWND {
263 self.hwnd.0.clone()
264 }
265}
266
267impl Drop for DisplayWindowManager {
268 fn drop(&mut self) {
269 unsafe {
270 self.should_exit.store(true, Ordering::Relaxed);
271
272 log::trace!("Destroying window...");
273 let res = PostMessageW(Some(self.hwnd.0), WM_DESTROY_WINDOW, WPARAM(0), LPARAM(0));
274 if let Err(err) = res {
275 log::error!("Failed to post destroy window message: {:?}", err);
276 }
277
278 let thread = self.message_thread.take();
279 if let Some(thread) = thread {
280 log::trace!("Waiting for message thread to exit...");
281 thread.join().unwrap();
282 }
283 }
284 }
285}